#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include "FreeImage.h"
#include "raylib.h"
#include "raymath.h"
#include "pub_lib.h"
#include "image.h"

//tiff lib
FIBITMAP* open_img(char* path)
{
	FIBITMAP* dib = NULL;
	const char *name;
	const char *ext;
	name = GetFileName(path);
	ext = GetFileExtension(name);
	if (!ext)
		return NULL;
	if(!strcmp(ext, ".tif"))
		dib = FreeImage_Load(FIF_TIFF, path, TIFF_DEFAULT);
	return dib;
}

void read_img(FIBITMAP* dib, cpu_image* ptr)
{
	ptr->image_width = FreeImage_GetWidth(dib);
	ptr->image_height = FreeImage_GetHeight(dib);

	ptr->bpp = FreeImage_GetBPP(dib);
	PixelFormat format;
	switch (ptr->bpp) {
		case 24:
			format = PIXELFORMAT_UNCOMPRESSED_R8G8B8;
			break;
		case 1:
			format = PIXELFORMAT_UNCOMPRESSED_GRAYSCAL_1;
			//if 0 is white, invert it
			binary_image_invert(dib);
			break;
		case 8:
		default:
			format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE;
			break;
	}
	ptr->format = format;
}

FIBITMAP* copy_img(FIBITMAP* dib)
{
	int w, h;
	FIBITMAP* new;

	w = FreeImage_GetWidth(dib);
	h = FreeImage_GetHeight(dib);

	new = FreeImage_Copy(dib, 0, 0, w, h);
	return new;
}

/*
 *	dst = ori + c * add
 */
void image_add(FIBITMAP* ori, FIBITMAP* add, FIBITMAP* dst, int c)
{
	int x, y;
	BYTE value, avalue;
	int sum;

	int w, h;
	w = FreeImage_GetWidth(ori);
	h = FreeImage_GetHeight(ori);
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			FreeImage_GetPixelIndex(ori, x, y, &value);
			FreeImage_GetPixelIndex(add, x, y, &avalue);
			sum = value + c * avalue;
			if (sum < 0)
				sum = 0;
			if(sum > 255)
				sum = 255;
			value = (BYTE)sum;
			FreeImage_SetPixelIndex(dst, x, y, &value);
		}
	}
}

/*
 *	dst = ori + c * add
 */
void mem_add(BYTE* ori, BYTE* add, BYTE* dst, int c, int w, int h)
{
	int x, y, index;
	BYTE value, avalue;
	int sum;

	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			index = y * w + x;
			value = ori[index];
			avalue = add[index];
			sum = value + c * avalue;
			if (sum < 0)
				sum = 0;
			if(sum > 255)
				sum = 255;
			value = (BYTE)sum;
			dst[index] = value;
		}
	}
}

/*
 *	dst = ori * mul
 */
void image_multi(FIBITMAP* ori, FIBITMAP* mul, FIBITMAP* dst)
{
	int x, y;
	BYTE value, avalue;
	int sum;

	int w, h;
	w = FreeImage_GetWidth(ori);
	h = FreeImage_GetHeight(ori);
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			FreeImage_GetPixelIndex(ori, x, y, &value);
			FreeImage_GetPixelIndex(mul, x, y, &avalue);
			sum = value * avalue / 64;
			//确保最终结果在 [0, 255]，否则会溢出，图片显示错误
			if (sum < 0)
				sum = 0;
			if (sum > 255)
				sum = 255;
			value = (BYTE)sum;
			FreeImage_SetPixelIndex(dst, x, y, &value);
		}
	}
}

/* use data[h][w] fill image */
void fill_image_data(FIBITMAP* dib, BYTE* data)
{
	int w, h, y, x;
	w = FreeImage_GetWidth(dib);
	h = FreeImage_GetHeight(dib);
	unsigned char value;
	int index;
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			index = get_window_index(y, x, w);
			value = data[index];
			FreeImage_SetPixelIndex(dib, x, y, &value);
		}
	}
}

/* use data[h][w] fill image
 * data[index] is normalized data: [0, 1]
 * */
void fill_image_data_float(FIBITMAP* dib, float* data)
{
	int w, h, y, x;
	w = FreeImage_GetWidth(dib);
	h = FreeImage_GetHeight(dib);
	unsigned char value;
	int index;
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			index = get_window_index(y, x, w);
			value = 255 * data[index];
			FreeImage_SetPixelIndex(dib, x, y, &value);
		}
	}
}

//flag: r = 0, g = 1, b = 2
//used r/g/b fill dst (gray)
void fill_image_data_color(FIBITMAP* color, FIBITMAP* dst, int flag)
{
	int w, h, y, x, index;
	w = FreeImage_GetWidth(color);
	h = FreeImage_GetHeight(color);
	unsigned char value;
	RGBQUAD rgb;
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			index = get_window_index(y, x, w);
			FreeImage_GetPixelColor(color, x, y, &rgb);
			if(flag == 0)
				value = rgb.rgbRed;
			else if(flag == 1)
				value = rgb.rgbGreen;
			else
				value = rgb.rgbBlue;
			FreeImage_SetPixelIndex(dst, x, y, &value);
		}
	}
}

//flag: r = 0, g = 1, b = 2
//used src (gray) to fill color (rgb)
void fill_color_image_gray(FIBITMAP* color, FIBITMAP* src, int flag)
{
	int w, h, y, x;
	w = FreeImage_GetWidth(color);
	h = FreeImage_GetHeight(color);
	unsigned char value;
	RGBQUAD rgb;

	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			FreeImage_GetPixelColor(color, x, y, &rgb);
			FreeImage_GetPixelIndex(src, x, y, &value);
			if(flag == 0)
				rgb.rgbRed = value;
			else if(flag == 1)
				rgb.rgbGreen = value;
			else
				rgb.rgbBlue = value;
			FreeImage_SetPixelColor(color, x, y, &rgb);
		}
	}
}


//read image data and write to data[h][w]
void init_image_data(FIBITMAP* dib, float* data)
{
	int w, h, y, x;
	w = FreeImage_GetWidth(dib);
	h = FreeImage_GetHeight(dib);
	//fill input data
	unsigned char value;
	int index;
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			FreeImage_GetPixelIndex(dib, x, y, &value);
			index = get_window_index(y, x, w);
			data[index] = value;
		}
	}
}

//read image data and write to data[h][w]
void init_image_data_normalized(FIBITMAP* dib, float* data)
{
	int w, h, y, x;
	w = FreeImage_GetWidth(dib);
	h = FreeImage_GetHeight(dib);
	//fill input data
	unsigned char value;
	int index;
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			FreeImage_GetPixelIndex(dib, x, y, &value);
			index = get_window_index(y, x, w);
			data[index] = (float)value / 255;
		}
	}
}


//read image data and write to data[h][w]
void init_image_datai(FIBITMAP* dib, int* data)
{
	int w, h, y, x;
	w = FreeImage_GetWidth(dib);
	h = FreeImage_GetHeight(dib);
	//fill input data
	unsigned char value;
	int index;
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			FreeImage_GetPixelIndex(dib, x, y, &value);
			index = get_window_index(y, x, w);
			data[index] = value;
		}
	}
}

//read image data and write to data[h][w]
void init_image_databv(FIBITMAP* dib, BYTE* data)
{
	int w, h, y, x;
	w = FreeImage_GetWidth(dib);
	h = FreeImage_GetHeight(dib);
	//fill input data
	unsigned char value;
	int index;
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			FreeImage_GetPixelIndex(dib, x, h - 1 - y, &value);
			index = get_window_index(y, x, w);
			data[index] = value;
		}
	}
}

/* use data[h][w] fill image */
void fill_image_datav(FIBITMAP* dib, BYTE* data)
{
	int w, h, y, x;
	w = FreeImage_GetWidth(dib);
	h = FreeImage_GetHeight(dib);
	unsigned char value;
	int index;
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			index = get_window_index(y, x, w);
			value = data[index];
			FreeImage_SetPixelIndex(dib, x, h - 1 - y, &value);
		}
	}
}

//read image data and write to data[h][w]
void init_image_datab(FIBITMAP* dib, unsigned char* data)
{
	int w, h, y, x;
	w = FreeImage_GetWidth(dib);
	h = FreeImage_GetHeight(dib);

	unsigned int bpp;
	int fill_flag = 0; //1 to 255
	bpp = FreeImage_GetBPP((FIBITMAP*)dib);
	if(bpp == 1) {
		fill_flag = 1;
	}

	//fill input data
	unsigned char value;
	int index;
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			FreeImage_GetPixelIndex(dib, x, y, &value);
			index = get_window_index(y, x, w);
			if(fill_flag && value) //1 to 255
				data[index] = 255;
			else
				data[index] = value;
		}
	}
}

//flag: r = 0, g = 1, b = 2
//used r/g/b fill data[h][w]
void init_image_datab_color(FIBITMAP* color, unsigned char* data, int flag)
{
	int w, h, y, x, index;
	w = FreeImage_GetWidth(color);
	h = FreeImage_GetHeight(color);
	unsigned char value;
	RGBQUAD rgb;
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			index = get_window_index(y, x, w);
			FreeImage_GetPixelColor(color, x, y, &rgb);
			if(flag == 0)
				value = rgb.rgbRed;
			else if(flag == 1)
				value = rgb.rgbGreen;
			else
				value = rgb.rgbBlue;
			data[index] = value;
		}
	}
}


//read image data and write to data[h][w]
void init_image_center_data(FIBITMAP* dib, float* data)
{
	int w, h, y, x;
	w = FreeImage_GetWidth(dib);
	h = FreeImage_GetHeight(dib);
	//fill input data
	unsigned char value;
	float v;
	int index;
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			FreeImage_GetPixelIndex(dib, x, y, &value);
			index = get_window_index(y, x, w);
			if ((x + y) & 1)
				v = -value;
			else
				v = value;
			data[index] = v;
		}
	}
}

//src[h][w] to center dst[h][w]
//dst may same address of src
void move_data_to_center(float* src, float* dst, int w, int h)
{
	int x, y, index;
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			index = get_window_index(y, x, w);
			if ((x + y) & 1)
				dst[index] = -src[index];
			else
				dst[index] = src[index];
		}
	}

}

/*
 * fb_width, fb_height: 窗口的宽，高
 * startx, starty: 相对于窗口的坐标
 * */
void image_update_frameBuf(FIBITMAP* dib,
		Color* frame_buf, int fb_width, int fb_height,
		int startx, int starty)
{
	//fill framebuf
	int x, y, index;
	Color c;
	BYTE value;

	int w, h;
	w = FreeImage_GetWidth(dib);
	h = FreeImage_GetHeight(dib);

	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			FreeImage_GetPixelIndex(dib, x, y, &value);
			c = (Color){ value, value, value, 255 };
			index = get_window_index(starty + h - y,\
				x + startx, fb_width);
			frame_buf[index] = c;
		}
	}
}

void image_update_frameBuf_c(FIBITMAP* dib,
	Color* frame_buf, int fb_width, int fb_height,
	int startx, int starty)
{
	//fill framebuf
	int x, y, index;
	Color c;
	RGBQUAD value;

	int w, h;
	w = FreeImage_GetWidth(dib);
	h = FreeImage_GetHeight(dib);

	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			FreeImage_GetPixelColor(dib, x, y, &value);
			c = (Color){ value.rgbRed, value.rgbGreen, value.rgbBlue, 255};
			index = get_window_index(starty + h - y, \
				x + startx, fb_width);
			frame_buf[index] = c;
		}
	}
}

/*
 * fb_width, fb_height: 窗口的宽，高
 * startx, starty: 相对于窗口的坐标
 * */
void image_data_update_frameBuf(BYTE* data, int w, int h,
	Color* frame_buf, int fb_width, int fb_height,
	int startx, int starty)
{
	//fill framebuf
	int x, y, index;
	Color c;
	BYTE value;

	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			value = data[y * w + x];
			c = (Color){ value, value, value, 255 };
			index = get_window_index(starty + h - y, x + startx, fb_width);
			frame_buf[index] = c;
		}
	}
}

void image_update_frameBuf_bin(FIBITMAP* dib,
	Color* frame_buf, int fb_width, int fb_height,
	int startx, int starty)
{
	//fill framebuf
	int x, y, index;
	Color c;
	BYTE value;

	int w, h;
	w = FreeImage_GetWidth(dib);
	h = FreeImage_GetHeight(dib);
	BYTE* bits;

	for (y = 0; y < h; y++) {
		bits = FreeImage_GetScanLine(dib, y);
		for (x = 0; x < w; x++) {
			value = (bits[x >> 3] & (0x80 >> (x & 7))) != 0;
			if (value)
				value = 255;
			else
				value = 0;
			c = (Color){ value, value, value, 255 };
			index = get_window_index(starty + h - y, \
				x + startx, fb_width);
			frame_buf[index] = c;
		}
	}
}

void show_image(void* dib, cpu_image* ptr, int startx, int starty)
{
	unsigned int bpp;
	bpp = FreeImage_GetBPP((FIBITMAP*)dib);
	switch (bpp) {
	case 1:
		image_update_frameBuf_bin((FIBITMAP*)dib, ptr->frame_buf, \
			ptr->win_width, ptr->win_height, startx, starty);
		break;
	case 24:
		image_update_frameBuf_c((FIBITMAP*)dib, ptr->frame_buf, \
			ptr->win_width, ptr->win_height, startx, starty);
		break;
	case 8:
	default:
		image_update_frameBuf((FIBITMAP*)dib, ptr->frame_buf, \
			ptr->win_width, ptr->win_height, startx, starty);
		break;
	}
}

void show_image_data(BYTE* data, int w, int h,
	cpu_image* ptr, int startx, int starty)
{
	image_data_update_frameBuf(data, w, h, ptr->frame_buf,\
		ptr->win_width, ptr->win_height, startx, starty);
}

static void imshow(cpu_image *ptr, void *data, int pos,\
	right_image_type data_type, int w, int h, int x, int y)
{
	if(data_type == RIGHT_IMAGE_FILE) {
		show_image(data, ptr, x, y);
	} else {
		show_image_data((BYTE*)data, w, h, ptr, x, y);
	}
}

#define MIN_OFF_Y 30
static void cal_off(cpu_image* ptr, int w, int h, int* xoff, int* yoff)
{
	int offx, offy;
	offx = 5;
	offy = MIN_OFF_Y;
	offx = (ptr->win_width - ptr->split_line - w - w - 10) / 3;
	offy = (ptr->win_height - ptr->starty - h - h - 10) / 3;
	if (offx < 5)
		offx = 5;
	if (offy < MIN_OFF_Y)
		offy = MIN_OFF_Y;
	*xoff = offx;
	*yoff = offy;
}

/* draw right window
 * pos = 1, 2, 3, 4
 * */
static void draw_right_content(cpu_image *ptr, void *data, int pos,
	right_image_type data_type, char* str)
{
	int x, y, w, h;
	int xoff, yoff;

	if(!data)
		return;
	x = ptr->rx;
	y = ptr->starty;
	if(data_type == RIGHT_IMAGE_FILE) {
		w = FreeImage_GetWidth((FIBITMAP*)data);
		h = FreeImage_GetHeight((FIBITMAP*)data);
	} else {
		w = FreeImage_GetWidth(ptr->rchg);
		h = FreeImage_GetHeight(ptr->rchg);
	}

	if(ptr->right_window_num == 1) {
		if (!str) {
			if (data_type == RIGHT_IMAGE_HISTO) {

			}
			else {
				imshow(ptr, data, pos, data_type, w, h, x, y);
			}
			return;
		}
	}

	cal_off(ptr, w, h, &xoff, &yoff);
	switch(pos){
		//line 1
		case 1:
			break;
		case 2:
			x = ptr->rx + w + xoff;
			y = ptr->starty;
			break;
		//line 2
		case 3:
			x = ptr->rx;
			y = ptr->starty + h + yoff;
			break;
		case 4:
			x = ptr->rx + w + xoff;
			y = ptr->starty + h + yoff;
			break;
		default:
			return;
	}

	if(str)
		DrawText(str, x, y + h + 5, 20, BLACK);
	else {
		if(data_type == RIGHT_IMAGE_HISTO) {
			draw_histogram(ptr->frame_buf, ptr->win_width, x, y, x + w, y + h,
				(unsigned int *)data);
		} else {
			imshow(ptr, data, pos, data_type, w, h, x, y);
		}
	}
}

void draw_right_window(cpu_image *ptr, void *data, int pos, right_image_type data_type)
{
	draw_right_content(ptr, data, pos, data_type, NULL);
}

void draw_figure_title(cpu_image* ptr, char* str, int pos)
{
	draw_right_content(ptr, ptr->rchg, pos, 0, str);
}

void close_img(FIBITMAP* dib)
{
	FreeImage_Unload(dib);
}

void clear_fb_right(cpu_image* ptr, Color c)
{
	clear_fb_range(ptr->frame_buf, ptr->win_width, \
		ptr->rx, ptr->starty,\
		ptr->win_width - 1, ptr->win_height - 1, c);
}

void clear_fb(cpu_image* ptr, Color c)
{
	int w, h, y;
	y = ptr->starty;
	//clear left
	w = FreeImage_GetWidth(ptr->left);
	h = FreeImage_GetHeight(ptr->left);
	clear_fb_range(ptr->frame_buf, ptr->win_width,\
		ptr->lx, y, ptr->lx + w, y + h, c);

	//clear right
	/*
	w = FreeImage_GetWidth(ptr->right);
	h = FreeImage_GetHeight(ptr->right);
	clear_fb_range(ptr->frame_buf, ptr->win_width,\
		ptr->rx, y, ptr->rx + w, y + h, c);
	*/
	clear_fb_range(ptr->frame_buf, ptr->win_width,\
		ptr->rx, y, ptr->win_width - 1, ptr->win_height - 1, c);
}

/* 得到一幅图的直方图
 * unsigned int histo[256]
 * */
int get_histogram(FIBITMAP* dib, unsigned int* histo)
{
	BYTE pixel;
	BYTE *bits = NULL;
	unsigned x, y;

	unsigned width  = FreeImage_GetWidth(dib);
	unsigned height = FreeImage_GetHeight(dib);
	// clear histogram array
	memset(histo, 0, 256 * sizeof(unsigned int));
	// compute histogram for black channel
	for(y = 0; y < height; y++) {
		bits = FreeImage_GetScanLine(dib, y);
		for(x = 0; x < width; x++) {
			// get pixel value
			pixel = bits[x];
			histo[pixel]++;
		}
	}

	return 0;
}

/*
  * in: float data[h][w], normalized data [0, 1]
  * out: unsigned int histo[256]
 * */
void get_histogram_float(float* data, int width, int height, unsigned int* histo)
{
	BYTE pixel;
	int x, y, index;

	//clear histogram array
	memset(histo, 0, 256 * sizeof(unsigned int));
	//compute histogram for black channel
	for(y = 0; y < height; y++) {
		for(x = 0; x < width; x++) {
			//get pixel value
			index = y * width + x;
			pixel = data[index] * 255;;
			histo[pixel]++;
		}
	}
}

//min and max intensity of image
void image_get_intensity_range(FIBITMAP* dib, BYTE* min, BYTE* max)
{
	unsigned int x, y;
	BYTE value, v_min, v_max;

	unsigned int w, h;
	w = FreeImage_GetWidth(dib);
	h = FreeImage_GetHeight(dib);

	v_min = 255;
	v_max = 0;
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			FreeImage_GetPixelIndex(dib, x, y, &value);
			if(value > v_max)
				v_max = value;
			if(value < v_min)
				v_min = value;
		}
	}

	if(min)
		*min = v_min;
	if(max)
		*max = v_max;
}

/* normalize image to [0, 1]
 * save result to out[h][w]
 * */
void normalize_image(FIBITMAP* dib, float* out)
{
	unsigned int x, y, w, h;
	w = FreeImage_GetWidth(dib);
	h = FreeImage_GetHeight(dib);

	BYTE value;
	float a;
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			FreeImage_GetPixelIndex(dib, x, y, &value);
			a = value / 256.0f;
			out[y * w + x] = a;
		}
	}
}

void show_scaled_dib(FIBITMAP* dib, cpu_image *ptr, int w, int h, int show_pos)
{
	FIBITMAP *chg;
	chg = FreeImage_Rescale(dib, w, h, FILTER_BILINEAR);
	draw_right_window(ptr, (void*)chg, show_pos, RIGHT_IMAGE_FILE);
	close_img(chg);
}

/* show scaled image
 * open image from "file", and scaled to [h, w]
 * show_pos: right window index from 1 to 4
 * */
void show_scaled_image(char* file, cpu_image *ptr, int w, int h, int show_pos)
{
	char path[256];

	get_full_path(IMAGE_PATH, file, path);
	FIBITMAP *ori = open_img(path);
	if (!ori) {
		printf("file %s not exist\n", path);
		return;
	}
	show_scaled_dib(ori, ptr, w, h, show_pos);
	close_img(ori);
}

//BYTE data[h][w]: image data
//scaled to nw * nh, show at window pos
void show_scaled_data_common(BYTE* data, cpu_image *ptr,
	int w, int h, int pos, int nw, int nh)
{
	Image img;
	img.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE;
	img.data = malloc(w * h);
	img.width = w;
	img.height = h;
	memcpy(img.data, data, w * h);

	ImageResize(&img, nw, nh);
	draw_right_window(ptr, img.data, pos, RIGHT_IAMGE_DATA);

	free(img.data);
}

/*
 * show scaled filtered image in right window(show_pos)
 * in:
 *	file: image file name
 *	show_pos: right window index from 1 to 4
 *	w, h: window size
 *	filter_type: user define
 *	dim: dimension of spatial filter
 *	user_data: in-out user data
 * */
void show_filtered_image(char* file, cpu_image *ptr, int w, int h, int show_pos,
	pf_show_handled_image filter, int filter_type, int dim, void* user_data)
{
	FIBITMAP *ori, *chg, *show;
	char path[256];

	//3. Max
	get_full_path(IMAGE_PATH, file, path);
	ori = open_img(path);
	if (!ori) {
		printf("file %s not exist\n", path);
		return;
	}
	chg = copy_img(ori);

	filter(ori, chg, filter_type, dim, user_data);
	show = FreeImage_Rescale(chg, w, h, FILTER_BILINEAR);
	draw_right_window(ptr, (void*)show, show_pos, RIGHT_IMAGE_FILE);

	close_img(show);
	close_img(ori);
	close_img(chg);
}

HSITRIPLE rgb2hsi(RGBQUAD rgb)
{
	HSITRIPLE hsi;
	float r = rgb.rgbRed;
	float g = rgb.rgbGreen;
	float b = rgb.rgbBlue;
	float theta, numerator, denominator;

	numerator = ((r - g) + (r - b)) / 2;
	denominator = (r - g) * (r - g) + (r - b) * (g - b);
	denominator = sqrtf(denominator) + MIN_NUMBER;
	theta = acosf(numerator / denominator);
	theta = RAD2DEG * theta;
	if (b <= g)
		hsi.h = theta;
	else
		hsi.h = 360 - theta;

	float min, s, i;
	min = min(r, g);
	min = min(min, b);
	s = 3 * min / (r + g + b + MIN_NUMBER);
	hsi.s = 1 - s;

	i = (r + g + b) / 3;
	hsi.i = i / 255;
	return hsi;
}

RGBQUAD hsi2rgb(HSITRIPLE hsi)
{
	RGBQUAD rgb;
	rgb.rgbReserved = 0;

	float r, g, b;
	float h, s, i;
	h = hsi.h;
	s = hsi.s;
	i = hsi.i;

	float numerator, denominator;
	if(h >= 240) {
		h -= 240;
		g = i * (1 - s);
		numerator = s * cosf(DEG2RAD * h);
		denominator = cosf((60 - h) * DEG2RAD) + MIN_NUMBER;
		b = i * (1 + numerator / denominator);
		r = 3 * i - (g + b);
	} else if(h >= 120) {
		h -= 120;
		r = i * (1 - s);
		numerator = s * cosf(DEG2RAD * h);
		denominator = cosf((60 - h) * DEG2RAD) + MIN_NUMBER;
		g = i * (1 + numerator / denominator);
		b = 3 * i - (r + g);
	} else { //h < 120
		b = i * (1 - s);
		numerator = s * cosf(DEG2RAD * h);
		denominator = cosf((60 - h) * DEG2RAD) + MIN_NUMBER;
		r = i * (1 + numerator / denominator);
		g = 3 * i - (r + b);
	}

	if(r > 1)
		r = 1;
	if(g > 1)
		g = 1;
	if(b > 1)
		b = 1;
	rgb.rgbRed = r * 255;
	rgb.rgbGreen = g * 255;
	rgb.rgbBlue = b * 255;
	return rgb;
}

//RGB image to HSI buffer
void rgb2hsi_img(FIBITMAP* dib, float* hue, float* s, float* i)
{
	int w, h, x, y, index;
	w = FreeImage_GetWidth(dib);
	h = FreeImage_GetHeight(dib);

	RGBQUAD rgb;
	HSITRIPLE hsi;
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			FreeImage_GetPixelColor(dib, x, y, &rgb);
			hsi = rgb2hsi(rgb);
			index = get_window_index(y, x, w);
			hue[index] = hsi.h;
			s[index] = hsi.s;
			i[index] = hsi.i;
		}
	}
}

//HSI buffer to RGB image
void hsi2rgb_img(FIBITMAP* dib, float* hue, float* s, float* i)
{
	int w, h, x, y, index;
	w = FreeImage_GetWidth(dib);
	h = FreeImage_GetHeight(dib);

	RGBQUAD rgb;
	HSITRIPLE hsi;
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			index = get_window_index(y, x, w);
			hsi.h = hue[index];
			hsi.s = s[index];
			hsi.i = i[index];
			rgb = hsi2rgb(hsi);
			FreeImage_SetPixelColor(dib, x, y, &rgb);
		}
	}
}

//[0, 1] to [0, 255], data[h][w] to dst[h][w]
//type = 0, Hue; 1, Saturation; 2, Intensity
void convert_hsi_data(float* data, int type, int w, int h, unsigned char* dst)
{
	float scale = 255;
	if(type == 0) {
		//Hue, [0, 360]
		scale = 255.0f / 360.0f;
	}

	int x, y, index;
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			index = get_window_index(y, x, w);
			dst[index] = data[index] * scale;
		}
	}
}

//dst = a - b
void color_image_sub(FIBITMAP* a, FIBITMAP* b, FIBITMAP* dst)
{
	int w, h, x, y;
	w = FreeImage_GetWidth(a);
	h = FreeImage_GetHeight(a);

	RGBQUAD rgb_a;
	RGBQUAD rgb_b;
	RGBQUAD result;
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			FreeImage_GetPixelColor(a, x, y, &rgb_a);
			FreeImage_GetPixelColor(b, x, y, &rgb_b);
			result.rgbReserved = 0;
			result.rgbRed = rgb_a.rgbRed - rgb_b.rgbRed;
			result.rgbGreen = rgb_a.rgbGreen - rgb_b.rgbGreen;
			result.rgbBlue = rgb_a.rgbBlue - rgb_b.rgbBlue;
			FreeImage_SetPixelColor(dst, x, y, &result);
		}
	}
}

static const char tag_photo_interp[] = "PhotometricInterpretation";
//if WhitIsZero, invert it
void binary_image_invert(FIBITMAP* dib)
{
	int revert = 0;
	FITAG* tag = NULL;

#ifdef DEBUG
	FIMETADATA* meta;
	meta = FreeImage_FindFirstMetadata(FIMD_EXIF_MAIN, dib, &tag);
	if (!meta) {
		meta = FreeImage_FindFirstMetadata(FIMD_EXIF_EXIF, dib, &tag);
		if(!meta)
			return;
	}

	do {
		printf("%s\n", FreeImage_GetTagKey(tag));

	} while (FreeImage_FindNextMetadata(meta, &tag));
	FreeImage_FindCloseMetadata(meta);
#endif

	FreeImage_GetMetadata(FIMD_EXIF_MAIN, dib, tag_photo_interp, &tag);
	if (!tag) {
		FreeImage_GetMetadata(FIMD_EXIF_EXIF, dib, tag_photo_interp, &tag);
		if(!tag)
			return;
	}

	FREE_IMAGE_MDTYPE type = FreeImage_GetTagType(tag);
	if (type == FIDT_SHORT) {
		short* val = (short*)FreeImage_GetTagValue(tag);
		if (*val == 0) //white = 0
			revert = 1;
	}
	if (revert)
		FreeImage_Invert(dib);
}


